﻿using System;
using System.Collections.Generic;
using System.Data;
using Gumini.JulaDB.Enums;
using Gumini.JulaDB.Provider;

namespace Gumini.JulaDB.DataTypes
{
    /// <summary>
    /// A type of data stored in a table column.
    /// </summary>
    public abstract class DataType : IEquatable<DataType>
    {
        #region Instance members

        protected DataType(JulaDbType id, DbType dbType, Type managedType)
        {
            this.Id = id;
            this.DbType = dbType;
            this.ManagedType = managedType;
        }

        /// <summary>
        /// Data type id uniquely identifying the type.
        /// </summary>
        public JulaDbType Id { get; private set; }

        /// <summary>
        /// Gets the ADO.NET db type.
        /// </summary>
        public DbType DbType { get; private set; }

        /// <summary>
        /// SQL names for the data type. There might be multiple synonymous names for a single type.
        /// </summary>
        public abstract IEnumerable<string> Names { get; }

        /// <summary>
        /// Managed types that will be converted to this type.
        /// </summary>
        public abstract IEnumerable<Type> SourceTypes { get; }

        /// <summary>
        /// Managed .NET type used to represent the value in memory.
        /// </summary>
        public Type ManagedType { get; private set; }

        public abstract int Compare(object a, object b);

        protected abstract object ConvertCore(object source);

        public object ConvertFrom(object source)
        {
            // no conversion is required
            if (source == null || source.GetType().Equals(this.ManagedType))
                return source;

            // otherwise we try to perform type conversion
            try
            {
                return ConvertCore(source);
            }
            catch (Exception)
            {
                throw new JulaDbException(ErrorCode.TypeError, "Unable to convert '{0}' to type {1}.", source, this.Id);
            }
        }

        public bool Equals(DataType other)
        {
            return this.Id == other.Id;
        }

        #endregion

        #region Static members

        private static readonly IDictionary<string, DataType> _names = new Dictionary<string, DataType>();
        private static readonly IDictionary<JulaDbType, DataType> _ids = new Dictionary<JulaDbType, DataType>();
        private static readonly IDictionary<Type, DataType> _managedTypes = new Dictionary<Type, DataType>();
        private static readonly IDictionary<DbType, DataType> _dbTypes = new Dictionary<DbType, DataType>();

        static DataType()
        {
            Add(new VarcharDateType());
            Add(new BooleanDataType());
            Add(new NumberDataType());
        }

        private static void Add(DataType type)
        {
            foreach(var name in type.Names)
                _names.Add(name, type);
            _ids.Add(type.Id, type);
            _dbTypes.Add(type.DbType, type);
            foreach(var t in type.SourceTypes)
                _managedTypes.Add(t, type);
        }

        public static DataType GetType(string name)
        {
            if (!_names.ContainsKey(name))
                throw new JulaDbException(ErrorCode.UnknownDataType, "Unknown data type name: '{0}'.", name);
            return _names[name];
        }

        public static DataType GetType(JulaDbType id)
        {
            if (!_ids.ContainsKey(id))
                throw new JulaDbException(ErrorCode.UnknownDataType, "Unknown data type id: {0}.", id);
            return _ids[id];
        }

        public static DataType GetType(DbType dbType)
        {
            if (!_dbTypes.ContainsKey(dbType))
                throw new JulaDbException(ErrorCode.UnknownDataType, "Unknown DbType: {0}.", dbType);
            return _dbTypes[dbType];
        }

        public static DataType GetType(Type managedType)
        {
            if (!_managedTypes.ContainsKey(managedType))
                throw new JulaDbException(ErrorCode.UnknownDataType, "Unsupported managed data type: {0}.", managedType);
            return _managedTypes[managedType];
        }

        public static DataType Number
        {
            get { return _ids[JulaDbType.Number]; }
        }

        public static DataType Varchar
        {
            get { return _ids[JulaDbType.Varchar]; }
        }

        public static DataType Bool
        {
            get { return _ids[JulaDbType.Boolean]; }
        }

        #endregion
    }
}
